/*
	Boris Taratutin & Jason Curtis
	11.14.2009
*/

#include <p18f2455.h>
#include <spi.h>
#include "h_SPI_InDepth_Example.h"

/*
An In-depth example of how to use SPI to talk to various peripherals.
Here, we talk to an EEPROM and an ADC
*/



/* Definitions within SPI.h file by MC18 Compiler
Turns out, the protocol for talking to an EEPROM is already mapped out by microchip, and matches
up with the EEPROM that we are using

//  25Cxxx EEPROM instruction set
#define   SPI_WREN          6              // write enable latch
#define   SPI_WRDI          4              // reset the write enable latch
#define   SPI_RDSR          5              // read status register
#define   SPI_WRSR          1              // write status register
#define   SPI_READ          3              // read data from memory
#define   SPI_WRITE         2              // write data to memory

//  Bits within status register of 25Cxxx 
#define   WIP           	0              // write in progress status
#define   WEL           	1              // write enable latch status
#define   BP0           	2              // block protection bit status
*/

unsigned char var;	

// Pin definitions for SPI devices
#define eeprom_cs		LATBbits.LATB2			// Pin that controls the 'Chip Select' function for the EEPROM
#define adc_cs 			LATBbits.LATB3			// Pin that controls the 'Chip Select' function for the AD Converter


/*	Chip Select Functions	*/
void spiSelectEEprom(void)
{
	eeprom_cs = 0;					// Select EEPROM
	adc_cs = 1;						// Deselect ADC
}	
void spiSelectADC(void)
{
	adc_cs = 0;						// Select ADC
	eeprom_cs = 1;					// Deselect EEPROM
}	
void spiDeselectAll(void)
{
	eeprom_cs = 1;					// Deselect EEPROM
	adc_cs = 1;						// Deselect ADC
}	


/*	EEPROM Functions	*/


// Set Write-Enable
void spiEEpromWriteEnable(void)
{
	spiSelectEEprom();				//assert chip select
	var = putcSPI(SPI_WREN); 		//send "Set Write Enable Latch" command
	spiDeselectAll();				//negate chip select
}
	


// Read a Byte from the EEPROM
unsigned char spiEEpromReadByte (unsigned int address)
{
	// Convert address to sendable bytes
	unsigned char addr_low, addr_high;
	addr_low  = (unsigned char) (address & 0xff);
	addr_high = (unsigned char) ((address >> 8) & 0xff);
	
	spiSelectEEprom(); 				//assert chip select
	var = putcSPI(SPI_READ); 		//send read command
	var = putcSPI(addr_high); 		//send high byte of address
	var = putcSPI(addr_low); 		//send low byte of address
	var = getcSPI(); 				//read single byte
	spiDeselectAll();
	return (var);
}


// Write a Byte to the EEPROM(precondition: write register enabled)
unsigned char spiEEpromWriteByte (unsigned int address, unsigned char data)
{
	// Convert address to sendable bytes
	unsigned char var2;
	unsigned char addr_low, addr_high;
	addr_low  = (unsigned char) (address & 0xff);
	addr_high = (unsigned char) ((address >> 8) & 0xff);
	
	// Enable the write register (necessary for writing!) (disabled after every write cycle)
	spiEEpromWriteEnable();
	
	// Use EEPROM Protocol to send byte of data to given address over SPI
	spiSelectEEprom(); 					// assert chip select
	var = putcSPI(SPI_WRITE); 			// send write command
	var = putcSPI(addr_high); 			// send high byte of address
	var = putcSPI(addr_low); 			// send low byte of address
	var = putcSPI(data); 				// send data byte
	spiDeselectAll(); 					// negate chip select
	
	// Wait until byte finishes writing (writing begins after CS pin goes high)
	spiEEpromWriteWait();
	
	return var;
}

void page_write (unsigned int address, unsigned char *arr_to_write)
{
	// Convert address to sendable bytes
	unsigned char addr_low, addr_high;
	addr_low  = (unsigned char) (address & 0xff);
	addr_high = (unsigned char) ((address >> 8) & 0xff);
	
	// Enable the write register (necessary for writing!) (disabled after every write cycle)
	spiEEpromWriteEnable();
		
	spiSelectEEprom(); 					//assert chip select
	var = putcSPI(SPI_WRITE); 			//send write command
	var = putcSPI(addr_high); 			//send high byte of address
	var = putcSPI(addr_low); 			//send low byte of address
	putsSPI(arr_to_write); 				//send data array ("string")
	spiDeselectAll(); 					//negate chip select
	
	// Wait until byte finishes writing (writing begins after CS pin goes high)
	spiEEpromWriteWait();

}

// Write 2 large values to eeprom (up to 12 bytes each)
void spiEEpromWritePage (unsigned int address, unsigned short int *vals_to_write)
{
	unsigned char addr_low, addr_high;
	unsigned char arr_to_write[3];
	unsigned char oneHi, oneLo, twoHi, twoLo;
	unsigned char i;
	
	// Convert address to sendable bytes
	addr_low  = (unsigned char) (address & 0xff);
	addr_high = (unsigned char) ((address >> 8) & 0xff);
	
	// Convert 2 short int vals into 3 writable bytes
	oneLo = (unsigned char) (vals_to_write[0] & 0xff);
	oneHi = (unsigned char) (vals_to_write[0] >> 8) & 0b1111;
	
	twoLo = (unsigned char) (vals_to_write[1] & 0xff);
	twoHi = (unsigned char) (vals_to_write[1] >> 8) & 0b1111;
	
	arr_to_write[0] = oneLo;
	arr_to_write[1] = (unsigned char) ((oneHi << 4) + (twoHi));
	arr_to_write[2] = twoLo;

	// Iterate through array and write all vals to eeprom via spi
	for (i = 0; i <= 2; i++)
	{
		spiEEpromWriteByte(address + i, arr_to_write[i]);	
	}
	
}


// Read the status
unsigned char spiReadStatus (void)
{
	spiSelectEEprom();				//assert chip select
	var = putcSPI(SPI_RDSR); 		//send read status command
	var = getcSPI(); 				//read data byte
	spiDeselectAll(); 				//negate chip select
	return (var);
}

// Set protection level
// for data... (0 = no protection, 1 = 1/4 protection, 2 = 1/2 protection, 3 = full protection). Protected addresses are read-only
void spiWriteStatus (unsigned char data)
{
	spiSelectEEprom();
	var = putcSPI(SPI_WRSR); 		//write status command
	var = putcSPI(data); 			//status byte to write
	spiDeselectAll(); 				//negate chip select
}

// Set all eeprom vals to 0
void resetEEPROM(unsigned int eeprom_size)
{
	unsigned int i;
	unsigned char read_val;

	/*
	if(eeprom_size >= 65535) 			// If the eeprom size is 512K or larger, the chips have chip-erase cycles
	{
			spiSelectEEprom();				// Select the eeprom
			putcSPI(0b11000111); 			// send the 'chip clear' command
			spiDeselectAll(); 				// negate chip select
	}*/

	spiEEpromWriteEnable();				// Enable the write cycle
	
	for (i = 0; i < eeprom_size; i++)
	{
		read_val = spiEEpromReadByte(i);
		if (read_val != 0)			// If the byte isn't already cleared...
			spiEEpromWriteByte(i, 0);		// Write byte at (low address, high address, data)
		//page_write(i, blank_arr);
	}
}


// Loop while the system is still writing the byte to the eeprom
void spiEEpromWriteWait (void)
{
	do
	{
		spiSelectEEprom(); 				//assert chip select
		var = putcSPI(SPI_RDSR); 		//send read status command
		var = getcSPI(); 				//read data byte
		spiDeselectAll(); 				//negate chip select
	}
	while (var & 1);					// (masks to get only first bit. First bit is the status, which is 1 if write cycle is still in progress, 0 if done)
}



/* 13-bit Analog-Digital Converter */



// Takes a reading from the given channel of the external MCP3302/04 ADC:
// channel can be 0-7 
// se_diff is 1 for single-ended reading, 0 for differential
// returns an integer between 0 (corresponding to -vref) and 2047 (+vref).
// 0V will be coded as 1024 (0b10000000000 - 10 zeros).
// disregards 2 least-significant bits.

unsigned int spiReadADC(unsigned char channel, unsigned char se_diff) 
{
	unsigned char adc_config_byte;
	unsigned char adc_input[2];
	unsigned int adc_out_int = 1;
	unsigned char adc_sign_bit;

	
	// prepare configuration bits
	adc_config_byte = 0b10000; 			// 1 is start signal
	if (se_diff){ adc_config_byte += 0b1000;}
	adc_config_byte += channel;

	INTCONbits.GIE = 0;					// Disable interrupts
	
	spiSelectADC();						// Set appropriate chip-select
	
	putcSPI(adc_config_byte);			//ADC will now begin sampling.
	// ADC will take two clock cycles to convert, then output a low null bit, and next 13 bits are sign bit and data.
	
	getsSPI(adc_input, 2);				// collect two bytes via SPI
	INTCONbits.GIE = 1; 				// we're done with SPI; enable interrupts
	spiDeselectAll();					// put CS back high
	
	adc_sign_bit = adc_input[0] << 2 >> 7;		// sign bit is 1 for negative and 0 for positive; we will switch that
	adc_input[0] = adc_input[0] & 0b00011111;	// contains 6 most-significant bits. we remove extra bits here, including the sign bit (so we have 5 MSBs left).
	adc_input[1] = adc_input[1] >> 3;			// contains 7 least-significant bits. we throw away least significant 2 bits and 1 repeated bit (so we have 5 LSBs left).
	adc_out_int =  adc_input[1] + 32 * adc_out_int*(adc_input[0] + 32 * !adc_sign_bit); // concatenate, switching sign bit.
	// adc_out_int will be between 0 (corresponding to -vref) and 2047 (+vref). 
	// 0V will be coded as 1024 (0b10000000000 -> positive sign bit followed by 10 zeros).
	
	return adc_out_int;
}

/* 

-----------------------------
format coming from ADC:
-----------------------------
value is sent as sign bit followed by binary data		
note: sign bit 0 for positive. sign bit is always 0 for single-ended inputs.

val	 |	sign	binary data
+4095|	0		1111 1111 1111
+4094|	0		1111 1111 1110
+1	 |	0		0000 0000 0001
0	 |	0		0000 0000 0000
-1	 |	1		1111 1111 1111
-4095|	1		0000 0000 0001
-4096|	1		0000 0000 0000


-----------------------------
configuration bits for ADC:
-----------------------------
bit 0: single-ended (1) or differential (0)
bits 1-3: channel select. Here's what the channel select bits do:

if single-ended:
bits  | output channel
0 0 0 |	0
0 0 1 |	1
...   |
1 1 1 | 7

if differential:
bits  | +	-
0 0 0 | 0	1
0 0 1 |	1	0
0 1 0 | 2	3
0 1 1 |	3	2
1 0 0 | 4	5
1 0 1 | 5	4
1 1 0 | 6	7
1 1 1 | 7	6
*/










/* Not Currently Used */

/*
void spiEEpromReadArray(unsigned int address, unsigned char array_length, unsigned char *return_array)
{
	spiSelectEEprom(); 						//assert chip select
	var = putcSPI(SPI_READ); 			//send read command
	//var = putcSPI(address);				// start at 0 address	// Can't do! must send only bytes!
	getsSPI(return_array, array_length); 		//read multiple bytes
	spiDeselectAll();
}


void page_write (unsigned char addhigh, unsigned char addlow, unsigned char *wrptr)
{
	spiSelectEEprom(); 				//assert chip select
	var = putcSPI(SPI_WRITE); 	//send write command
	var = putcSPI(addhigh); 	//send high byte of address
	var = putcSPI(addlow); 		//send low byte of address
	putsSPI(wrptr); 			//send data byte
	spiDeselectAll(); 				//negate chip select
	

	// Try using putsSPI
	spiEEpromWriteEnable();
	
	spiSelectEEprom(); 					//assert chip select
	var = putcSPI(SPI_WRITE); 		//send write command
	var = putcSPI(addr_high); 		//send high byte of address
	var = putcSPI(addr_low); 		//send low byte of address
	putsSPI(arr_to_write); 			//send data array ("string")
	spiDeselectAll(); 					//negate chip select
	
	// Wait until byte finishes writing (writing begins after CS pin goes high)
	spiEEpromWriteWait();

}

// Read an array of length count, and store it's value in rdptr
void array_read (unsigned int address, unsigned char *rdptr, unsigned char count)
{
	spiSelectEEprom(); 				//assert chip select
	var = putcSPI(SPI_READ); 	//send read command
	var = putcSPI(address);
	getsSPI(rdptr, count); 		//read multiple bytes
	spiDeselectAll();
}


// Turns off Write-Enable (Write-disable) (*** Does it have to go before every write command? Or just before a write sequence?)
void spiEEpromWriteDisable(void)
{
	// Based on my experiments, this is not necessary to include. Write functionality is auto-disabled after each write
	spiSelectEEprom(); 				//assert chip select
	var = putcSPI(SPI_WRDI); 		//send "Reset Write Enable Latch" command
	spiDeselectAll(); 				//negate chip select	
}

*/



